Optimisation système d'une boucle d'apprentissage Resnet-50.

Le but de ce notebook est d'optimiser un code d'apprentissage d'un modèle Resnet-50 sur Imagenet pour Jean Zay en implémentant :
Les cellules dans ce notebook ne sont pas prévues pour être modifiées, sauf rares exceptions indiquées dans les commentaires. Les TP se feront en modifiant les codes dlojz1_X.py.
Les directives de modification seront marquées par l'étiquette TODO dans le notebook suivant.
Les solutions sont présentes dans le répertoire solutions/.
Notebook rédigé par l'équipe assistance IA de l'IDRIS, juin 2023
Un module PyTorch doit avoir été chargé pour le bon fonctionnement de ce Notebook. Nécessairement, le module pytorch-gpu/py3/2.1.1 :
!module list
Currently Loaded Modulefiles: 1) cuda/11.8.0 5) openmpi/4.1.5-cuda 9) sparsehash/2.0.3 2) nccl/2.18.5-1-cuda 6) intel-mkl/2020.4 10) libjpeg-turbo/2.1.3 3) cudnn/8.7.0.84-cuda 7) magma/2.7.1-cuda 11) pytorch-gpu/py3/2.1.1 4) gcc/8.5.0(8.3.1:8.4.1) 8) sox/14.4.2 >
Les fonctions python de gestion de queue SLURM dévelopées par l'IDRIS et les fonctions dédiées à la formation DLO-JZ sont à importer.
Le module d'environnement pour les jobs et la taille des images sont fixés pour ce notebook.
TODO : choisir un pseudonyme (maximum 5 caractères) pour vous différencier dans la queue SLURM et dans les outils collaboratifs pendant la formation et la compétition.
from idr_pytools import display_slurm_queue, gpu_jobs_submitter, search_log
from dlojz_tools import controle_technique, compare, GPU_underthehood, plot_accuracy, lrfind_plot, imagenet_starter
MODULE = 'pytorch-gpu/py3/2.1.1'
image_size = 224
account = 'for@a100'
name = 'pseudo' ## Pseudonyme à choisir
assert name != 'pseudo' and name != '', 'please choose a pseudo'
Création d'un répertoire checkpoints/ si cela n'a pas déjà été fait.
!mkdir -p checkpoints
Pour afficher vos jobs dans la queue SLURM :
display_slurm_queue(name)
Done!
Remarque: Cette fonction est utilisée plusieurs fois dans ce notebook. Elle permet d'afficher la queue de manière dynamique, rafraichie toutes les 5 secondes. Elle ne s'arrête que lorsque la queue est vide. Si vous désirez reprendre la main sur le notebook, il vous suffira d'arrêter manuellement la cellule avec le bouton stop. Cela a bien sûr aucun impact les jobs soumis.
Si vous voulez retirer TOUS vos jobs de la queue SLURM, décommenter et exécuter la cellule suivante :
#!scancel -u $USER
Si vous voulez retirer UN de vos jobs de la queue SLURM, décommenter, compléter et exécuter la cellule suivante :
#!scancel <jobid>
Cette partie debug permet d'afficher les fichiers de sortie et les fichiers d'erreur du job.
Il est nécessaire dans la cellule suivante (en décommentant) d'indiquer le jobid correspondant sous le format suivant.
Remarque : dans ce notebook, lorsque vous soumettrez un job, vous recevrez en retour le numéro du job dans le format suivant : jobid = ['123456']. La cellule ci-dessous peut ainsi être facilement actualisée."
jobid = ['1493206']
Fichier de sortie :
%cat {search_log(contains=jobid[0])[0]}
/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `cat {search_log(contains=jobid[0])[0]}'
Fichier d'erreur :
%cat {search_log(contains=jobid[0], with_err=True)['stderr'][0]}
/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `cat {search_log(contains=jobid[0], with_err=True)['stderr'][0]}'
Pour comparer son code avec les solutions mises à disposition, la fonction suivante permet d'afficher une page HTML contenant un différentiel de fichiers texte.
s1 = "./dlojz1_1.py"
s2 = "./solutions/dlojz1_1.py"
compare(s1, s2)
Voir le résultat du différentiel de fichiers sur la page suivante (attention au spoil !) :
import os
import torchvision
import torchvision.transforms as transforms
import torch
import numpy as np
import matplotlib.pyplot as plt
transform = transforms.Compose([
transforms.RandomResizedCrop(224), # Random resize - Data Augmentation
transforms.RandomHorizontalFlip(), # Horizontal Flip - Data Augmentation
transforms.ToTensor(), # convert the PIL Image to a tensor
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))
])
train_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet',
transform=transform)
train_dataset
Dataset ImageNet
Number of datapoints: 1281167
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: train
StandardTransform
Transform: Compose(
RandomResizedCrop(size=(224, 224), scale=(0.08, 1.0), ratio=(0.75, 1.3333), interpolation=bilinear, antialias=warn)
RandomHorizontalFlip(p=0.5)
ToTensor()
Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=4,
shuffle=True)
batch = next(iter(train_loader))
print('X train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[0].shape, batch[0].dtype, batch[0].element_size()*batch[0].nelement()))
print('Y train batch, shape: {}, data type: {}, Memory usage: {} bytes'
.format(batch[1].shape, batch[1].dtype, batch[1].element_size()*batch[1].nelement()))
img = batch[0][0].numpy().transpose((1,2,0))
plt.imshow(img)
plt.axis('off')
labels_cls, labels_id = torch.load(os.environ['ALL_CCFRSCRATCH']+'/imagenet/meta.bin')
label = labels_cls[np.unique(labels_id)[batch[1][0].numpy()]]
_ = plt.title('label class: {}'.format(label[0]))
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
X train batch, shape: torch.Size([4, 3, 224, 224]), data type: torch.float32, Memory usage: 2408448 bytes Y train batch, shape: torch.Size([4]), data type: torch.int64, Memory usage: 32 bytes
val_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.CenterCrop(224),
transforms.ToTensor(), # convert the PIL Image to a tensor
transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225))])
val_dataset = torchvision.datasets.ImageNet(root=os.environ['ALL_CCFRSCRATCH']+'/imagenet', split='val',
transform=val_transform)
val_dataset
Dataset ImageNet
Number of datapoints: 50000
Root location: /gpfsscratch/idris/sos/commun/imagenet
Split: val
StandardTransform
Transform: Compose(
Resize(size=(256, 256), interpolation=bilinear, max_size=None, antialias=warn)
CenterCrop(size=(224, 224))
ToTensor()
Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
)
import torchvision.models as models
model = models.resnet50()
print('number of total parameters: {}'.format(sum([p.numel() for p in model.parameters()])))
print('number of trainable parameters: {}'.format(sum([p.numel() for p in model.parameters() if p.requires_grad])))
number of total parameters: 25557032 number of trainable parameters: 25557032
Ce TP consiste à appliquer le code baseline pour prendre en main les fonctionnalités de test et découvrir le code.
TODO :
dlojz.pyRemarque :
DON'T MODIFY dans le script ne doivent pas être modifiées.n_gpu = 1
batch_size = 128
command = f'./dlojz1_0.py -b {batch_size} --image-size {image_size} --test --no-pin-memory'
command
'./dlojz1_0.py -b 128 --image-size 224 --test --no-pin-memory'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['902250']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 12.24 images/second GPU throughput: 12.26 images/second epoch time: 104662.52 seconds ----------- training step time average (fwd/bkwd on GPU): 10.439463 sec (35.4%/64.7%) +/- 0.101509 loading step time average (IO + CPU to GPU transfer): 0.016333 sec +/- 0.001069
Le code baseline dlo-jz.py a été exécuté sur le CPU (contrairement à ce qui est indiqué par le contrôle technique) en mode test, soit sur 50 itérations.
Dans le prochain exercice nous verrons ensemble l'accélération sur 1 GPU.

Voir la documentation pytorch
TODO : dans le script dlojz1_1.py:
Définir la variable gpu et envoyer le modèle dans la mémoire du GPU.
Envoyer les batches d'images d'entrée et les labels associés sur le GPU, pour **les étapes de training et de *validation***.
n_gpu = 1
batch_size = 128
command = f'./dlojz1_1.py -b {batch_size} --image-size {image_size} --test'
command
'./dlojz1_1.py -b 128 --image-size 224 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['902342']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 872.73 images/second GPU throughput: 996.84 images/second epoch time: 1468.13 seconds ----------- training step time average (fwd/bkwd on GPU): 0.128406 sec (7.0%/96.3%) +/- 0.023480 loading step time average (IO + CPU to GPU transfer): 0.018261 sec +/- 0.101229
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['902357', '902358', '902359', '902360', '902361', '902362', '902363', '902365']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 8 Max GPU Memory Allocated: 2.94 GB, Troughput: 414.063 images/second Batch size per GPU: 16 Max GPU Memory Allocated: 2.95 GB, Troughput: 642.645 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 2.96 GB, Troughput: 815.903 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 5.40 GB, Troughput: 926.822 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 10.56 GB, Troughput: 946.921 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 20.89 GB, Troughput: 1031.949 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 41.56 GB, Troughput: 1031.227 images/second Batch size per GPU: 1024 CUDA out of memory Memory occupancy by Model part : 0.303 +/- 0.126 GB
Le dernier job a atteint le seuil CUDA Out Of Memory :
controle_technique([jobids[-1]])

Voir la documentation de l'IDRIS
TODO : dans le script dlojz1_2.py:
Importer les fonctionnalités liées à l'Automatic Mixed Precision.
Initialiser le scaler.
Implémenter l'autocasting (le changement de précision, FP32 à FP16) dans le forward , avec la ligne with autocast(): dans la boucle de training et la boucle de validation.
Implémenter le gradient scaling pour la seule boucle de training. Note: À la place des lignes loss.backward() et optimizer.step().
n_gpu = 1
batch_size = 128
command = f'./dlojz1_2.py -b {batch_size} --image-size {image_size} --test'
command
'./dlojz1_2.py -b 128 --image-size 224 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobid = ['xxxxx'] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['902431']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 1400.46 images/second GPU throughput: 1475.33 images/second epoch time: 914.90 seconds ----------- training step time average (fwd/bkwd on GPU): 0.086760 sec (11.8%/102.5%) +/- 0.027443 loading step time average (IO + CPU to GPU transfer): 0.004638 sec +/- 0.008110
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['903148', '903150', '903151', '903152', '903154', '903155', '903156', '903157']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
903157 gpu_p5 idris ssos040 R 2:21 1 jean-zay-iam32
Done!
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 1.80 GB, Troughput: 634.335 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 1.81 GB, Troughput: 1035.191 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 2.94 GB, Troughput: 1315.744 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 5.61 GB, Troughput: 1452.447 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 10.95 GB, Troughput: 1483.561 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 21.61 GB, Troughput: 1531.505 images/second Batch size per GPU: 1024 Max GPU Memory Allocated: 42.95 GB, Troughput: 1547.718 images/second Batch size per GPU: 2048 CUDA out of memory Memory occupancy by Model part : 0.357 +/- 0.165 GB
TODO : Choisir pour la suite du TP une taille de batch par GPU qui vous semble la plus pertinente selon le test précédent.
## Choisir un batch size optimal
bs_optim = 512
n_gpu = 1
command = f'./dlojz1_2.py -b {bs_optim} --image-size {image_size} --test'
command
'./dlojz1_2.py -b 512 --image-size 224 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['903167']
display_slurm_queue(name)
JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
903167 gpu_p5 idris ssos040 R 2:01 1 jean-zay-iam32
Done!
controle_technique(jobid)
Train throughput: 1333.10 images/second GPU throughput: 1424.82 images/second epoch time: 961.32 seconds ----------- training step time average (fwd/bkwd on GPU): 0.359345 sec (3.6%/121.6%) +/- 0.131946 loading step time average (IO + CPU to GPU transfer): 0.024724 sec +/- 0.050359

Voir la documentation pytorch
TODO : dans le script dlojz1_3.py:
Lors de l'envoie du modèle au GPU, configurer le paramètre memory_format avec l'option Channel Last Memory.
Lors de l'envoie des images d'entrée au GPU, configurer le paramètre memory_format avec l'option Channel Last Memory.
n_gpu = 1
command = f'./dlojz1_3.py -b {bs_optim} --image-size {image_size} --test'
command
'./dlojz1_3.py -b 512 --image-size 224 --test'
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobid = ['902659']
display_slurm_queue(name)
Done!
controle_technique(jobid)
Train throughput: 1998.77 images/second GPU throughput: 2557.61 images/second epoch time: 641.16 seconds ----------- training step time average (fwd/bkwd on GPU): 0.200187 sec (7.3%/102.7%) +/- 0.076333 loading step time average (IO + CPU to GPU transfer): 0.055970 sec +/- 0.100890
Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['902829', '902830', '902831', '902832', '902833', '902834', '902835', '902836']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 1.71 GB, Troughput: 710.628 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 1.73 GB, Troughput: 1216.280 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 3.05 GB, Troughput: 1879.866 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 5.71 GB, Troughput: 2247.065 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 10.99 GB, Troughput: 2480.633 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 21.61 GB, Troughput: 2572.160 images/second Batch size per GPU: 1024 Max GPU Memory Allocated: 42.95 GB, Troughput: 2670.202 images/second Batch size per GPU: 2048 CUDA out of memory Memory occupancy by Model part : 0.374 +/- 0.052 GB
Voir la documentation pytorch
TODO : dans le script dlojz1_3.py:
non_blocking=args.non_blocking, afin d'activer l'option non-blocking. Cette option sera expliqué plus tard lors de la définition d'un DataLoader distribué.Afin de mesurer l'impact de la taille de batch sur l'occupation mémoire et sur le throughput, la cellule suivante permet de soumettre plusieurs jobs avec des tailles de batch croissantes. Dans les cas où la mémoire est saturée et dépasse la capacité du GPU, le système renverra une erreur CUDA Out of Memory.
Soumission du job. Attention vous sollicitez les noeuds de calcul à ce moment-là.
Pour soumettre le job, veuillez basculer la cellule suivante du mode Raw NBConvert au mode Code.
Copier-coller la sortie jobids = ['xxxxx', ...] dans la cellule suivante.
Puis, rebasculer la cellule précédente en mode Raw NBConvert, afin d'eviter de relancer un job par erreur.
#jobids = ['902813', '902814', '902816', '902817', '902819', '902820', '902821', '902823']
display_slurm_queue(name)
Done!
GPU_underthehood(jobids)
Batch size per GPU: 16 Max GPU Memory Allocated: 1.71 GB, Troughput: 710.432 images/second Batch size per GPU: 32 Max GPU Memory Allocated: 1.73 GB, Troughput: 1208.015 images/second Batch size per GPU: 64 Max GPU Memory Allocated: 3.05 GB, Troughput: 1828.829 images/second Batch size per GPU: 128 Max GPU Memory Allocated: 5.71 GB, Troughput: 2134.837 images/second Batch size per GPU: 256 Max GPU Memory Allocated: 10.99 GB, Troughput: 2345.172 images/second Batch size per GPU: 512 Max GPU Memory Allocated: 21.61 GB, Troughput: 2430.248 images/second Batch size per GPU: 1024 Max GPU Memory Allocated: 42.95 GB, Troughput: 2497.420 images/second Batch size per GPU: 2048 CUDA out of memory Memory occupancy by Model part : 0.374 +/- 0.052 GB
